<?php
/**
 * Created by PhpStorm.
 * User: liuxin
 * Date: 2018/8/14
 * Time: 下午2:17
 */

namespace common\components;
use \InvalidArgumentException;
use Yii;
/**
 * Redis缓存策略类。
 * 功能:
 * 自动完成搜索优先redis再mysql策略
 * mysql数据callback回写redis
 */
class CacheRedis
{
    /**
     * 兼容老版本缓存key
     */
    private $oldKey = [
      'findUserByUnionId' => 'user_guid:union_id',
      'findUserByGuid'    => 'user_base_info:guid'
    ];

    /**
     * Redis句柄
     * @var Redis
     */
    private $redis;
    /**
     * 正在调用的方法名称
     * @var string
     */
    private $class;
    /**
     * 配置
     * @var array
     */
    private $config = [
      'prefix' => '',
      'expire' => 3600,       // 缓存过期时间
      'emptyExpire' => 10     // 空值的缓存过期时间
    ];

    /**
     * 可用的Redis操作方法名
     * @var array
     */
    private $actions = ['cache', 'clear', 'flush','value'];
    /**
     * 构造方法
     * @param Redis|null $redis
     */
    public function __construct($redis = null, $config = [])
    {
        $this->redis = $redis;
        $this->config = array_merge($this->config, $config);
    }
    /**
     * 设置缓存时间
     *
     * @param $time int 单位秒
     */
    public function expire($time)
    {
        $this->config['expire'] = $time;
    }
    /**
     * 处理缓存方法
     *
     * @param $object object 对象
     * @param $method string 方法名
     * @param $arguments array 参数列表
     * @return mixed
     * @throws InvalidArgumentException 如果方法不存在，抛出异常
     */
    public function get($object, $name, $arguments)
    {
        if ($this->redis === null) {
            throw new InvalidArgumentException("未初始化Redis变量。\n");
        }

        $this->class = get_class($object);
        if (strlen($name) < 5) {
            throw new InvalidArgumentException(sprintf("Method %s->%s does not exist", $this->class, $name));
        }

        $method = substr($name, 0, -5);
        $action = substr($name, -5);
        if (!in_array($action, $this->actions, true) === false) {
            throw new InvalidArgumentException(sprintf("Method %s->%s does not exist", $this->class, $method));
        }
        return $this->$action($object, $method, $arguments);
    }


    /**
     *
     * key-value
     * 生成缓存键名
     * @param $method
     * @param $arguments
     * @return string
     */
    private function Key($method, $arguments)
    {
        if (isset($this->oldKey[$method])){
            $args   = implode(':',$arguments);
            return sprintf('%s:%s', strtolower($this->oldKey[$method]) , $args);
        }
        //$class = str_replace('\\', '_', $this->class);
        $args = ($arguments === '*') ? '*' : md5(json_encode($arguments));
        return strtolower(sprintf('%s%s:%s', $this->config['prefix'], $method, $args));
    }

    /**
     * 获取某个方法返回的HASH数据。MHGET
     * 如果数据已经缓存，则直接读取缓存数据；
     * 如果数据未缓存，则调用实际方法获取数据。
     * 实际使用时，请在子类的注释加上`@method`注释，以便编辑器能够自动识别。
     * @return mixed
     * @throws InvalidArgumentException 如果方法不存在，抛出异常
     */
    private function value($object, $method, $arguments)
    {
        $key = $this->Key($method, $arguments);
        $data = $this->getHash($key);
        if (!empty($data) && is_array($data)) {
            return $data;
        }elseif(!empty($data)){
            $decodeData = json_decode($data, JSON_UNESCAPED_UNICODE);
            return $decodeData === null ? $data : $decodeData;
        }
        if (method_exists($object, $method) === false) {
            throw new InvalidArgumentException(sprintf("Method %s->%s does not exist", $this->class, $method));
        }
        $data = call_user_func_array([$object, $method], $arguments);
        $expire = empty($data) ? $this->config['emptyExpire'] : Helper::CreateExpireTime(Yii::$app->params['redis-expire']['time-length']);
        $this->setHash($key,$data);
        $this->redis->expire($key, $expire);
        return $data;
    }

    /**
     * 获取某个方法返回的数据。
     * 如果数据已经缓存，则直接读取缓存数据；
     * 如果数据未缓存，则调用实际方法获取数据。
     * 实际使用时，请在子类的注释加上`@method`注释，以便编辑器能够自动识别。
     * @return mixed
     * @throws InvalidArgumentException 如果方法不存在，抛出异常
     */
    private function cache($object, $method, $arguments)
    {
        $key = $this->Key($method, $arguments);
        $data = $this->redis->get($key);
        if (!empty($data)) {
            return $data;
        }
        if (method_exists($object, $method) === false) {
            throw new InvalidArgumentException(sprintf("Method %s->%s does not exist", $this->class, $method));
        }
        $data = call_user_func_array([$object, $method], $arguments);
        $expire = empty($data) ? $this->config['emptyExpire'] : Helper::CreateExpireTime(Yii::$app->params['redis-expire']['time-length']);
        $this->redis->set($key, $data);
        $this->redis->expire($key,$expire);
        return $data;
    }
    /**
     * 删除指定缓存，参数和原数据获取方法一样
     * @return mixed
     */
    private function clear($object, $method, $arguments)
    {
        return $this->redis->del($this->key($method, $arguments));
    }
    /**
     * 删除指定方法的所有缓存
     * @return bool
     */
    private function flush($object, $method, $arguments)
    {
        $key = $this->key($method, '*');
        $keys = $this->redis->keys($key);
        return $this->redis->del($keys);
    }

    private  function setHash($redisKey = '',$table = [],$expire = 0 ){
        if(empty($redisKey) || empty($table)) return false;

        $params = [$redisKey];
        foreach ($table as $key => $value){
            if(is_bool($value)){
                $value = (int)$value;
            }
            $params[] = $key;
            $params[] = $value;
        }
        if(count($params) > 1) Yii::$app->redis->executeCommand('HMSET',$params);
    }


    private  function getHash($redisKey = '',$fields = []){
        $data = $rs =  [];
        if(empty($fields)){
            $data = Yii::$app->redis->HGetall(trim($redisKey));

            $count = count($data);
            for($i = 0 ; $i < $count ; $i++){
                if($i % 2 != 0){
                    $rs[$data[$i - 1]] = $data[$i];
                }
            }
        }else{
            $fieldCount = count($fields);
            for($i = 0 ; $i < $fieldCount ; $i++){
                $rs[$fields[$i]] = Yii::$app->redis->HGet($redisKey,$fields[$i]);
            }
        }

        return $rs;
    }

}